﻿// Copyright (c) JosephZheng. All rights reserved. See License.txt in the project root for license

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.ServiceModel;
using System.Threading;
using log4net;
using Lyra.Cache.Server.Interface;
using Lyra.Cache.Server.Interface.Contract.ServerResult;

namespace Lyra.Cache.Server
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class CacheServer : ICacheServer, IDisposable
    {
        private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private Dictionary<string, CacheRegion> _regions = new Dictionary<string, CacheRegion>();
        private int _lockSeconds = 30;
        private long _memoryLimit = 0;

        private bool _memoryFull = false;
        private object _memoryLock = new object();
        private string _memoryLackAction = "block";
        private double _memoryLackRelesePercentage = 0.1;
        private int _transferCompressSize = 128 * 1024;

        private Thread _memwatcher = null;

        internal bool Fulled
        {
            get
            {
                if (_memoryLimit != 0)
                {
                    lock (_memoryLock)
                    {
                        return _memoryFull;
                    }
                }
                else return false;
            }
        }

        public CacheServer()
        {
            try
            {
                _lockSeconds = Int32.Parse(ConfigurationManager.AppSettings["CacheLockSeconds"]);
            }
            catch { }
            try
            {
                _transferCompressSize = Int32.Parse(ConfigurationManager.AppSettings["TransferCompressSize"]);
            }
            catch { }
            try
            {
                _memoryLimit = Int64.Parse(ConfigurationManager.AppSettings["CacheMemoryLimit"]);
            }
            catch { }
            try
            {
                string extensionpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Extensions");
                if (!Directory.Exists(extensionpath)) Directory.CreateDirectory(extensionpath);
                else
                {
                    string[] extensiondlls = Directory.GetFiles(extensionpath, "*.dll",SearchOption.AllDirectories);
                    
                }
            }
            catch { }



            if (_memoryLimit < 0) _memoryLimit = 0;
            if (_memoryLimit != 0)
            {
                try
                {
                    _memoryLackAction = ConfigurationManager.AppSettings["CacheMemoryLackAction"].ToLower();
                }
                catch { }
                try
                {
                    _memoryLackRelesePercentage = Double.Parse(ConfigurationManager.AppSettings["CacheMemoryLackRelesePercentage"]);
                }
                catch { }
                if (_memoryLackRelesePercentage < 0.1) _memoryLackRelesePercentage = 0.1;
                else if (_memoryLackRelesePercentage > 0.5) _memoryLackRelesePercentage = 0.5;

                GC.RegisterForFullGCNotification(10, 10);
                GC.AddMemoryPressure(_memoryLimit);
                _memwatcher = new Thread(new ThreadStart(WatchGC));
                _memwatcher.IsBackground = true;
                _memwatcher.Start();
            }
        }

        private void WatchGC()
        {
            while (true)
            {
                try
                {
                    GCNotificationStatus status = GC.WaitForFullGCComplete();
                    if (status == GCNotificationStatus.Succeeded)
                    {
                        lock (_memoryLock)
                        {
                            _memoryFull = GC.GetTotalMemory(true) >= _memoryLimit;
                        }
                        if (_memoryFull)
                        {
                            if (_memoryLackAction == "release")
                            {
                                lock (_regions)
                                {
                                    foreach (KeyValuePair<string, CacheRegion> kvp in _regions)
                                    {
                                        kvp.Value.Release(_memoryLackRelesePercentage);
                                    }
                                }
                            }
                        }
                    }
                }
                catch { }
                finally
                {
                    Thread.Sleep(5000);
                }
            }
        }

        private CacheRegion GetRegion(string region)
        {
            lock (_regions)
            {
                if (_regions.ContainsKey(region)) return _regions[region];
                else return null;
            }
        }

        public byte[] Create(string region, string schema, Dictionary<string, string> views, Dictionary<string, string> extensions)
        {
            ServerResult result = new ServerResult();
            lock (_regions)
            {
                if (_regions.ContainsKey(region))
                {
                    result.Code = CacheCode.RegionHadExist;
                }
                else
                {
                    try
                    {
                        _regions.Add(region, new CacheRegion(_lockSeconds, schema, views, extensions));
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Drop(string region)
        {
            ServerResult result = new ServerResult();
            lock (_regions)
            {
                if (_regions.ContainsKey(region))
                {
                    try
                    {
                        CacheRegion cr = _regions[region];
                        _regions.Remove(region);
                        cr.Dispose();
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Schema(string region)
        {
            SchemaServerResult result = new SchemaServerResult();
            lock (_regions)
            {
                if (_regions.ContainsKey(region))
                {
                    result.Code = _regions[region].CacheSchema;
                }
                else
                {
                    result.Code = CacheCode.RegionNotExist;
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Statistics()
        {
            StatisticsServerResult result = new StatisticsServerResult();
            lock (_regions)
            {
                try
                {
                    foreach (KeyValuePair<string, CacheRegion> kvp in _regions)
                    {
                        result.Statistics.Add(kvp.Key, kvp.Value.Statistics());
                    }
                }
                catch (Exception ex)
                {
                    _log.Fatal(ex.ToString());
                    result.Code = ex.Message;
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Memory()
        {
            MemoryServerResult result = new MemoryServerResult();
            try
            {
                GC.Collect();
                result.Used = GC.GetTotalMemory(true);
                result.Limit = _memoryLimit;
            }
            catch (Exception ex)
            {
                _log.Fatal(ex.ToString());
                result.Code = ex.Message;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Add(string region, string key, string obj, TimeSpan expire)
        {
            ServerResult result = new ServerResult();
            if (!Fulled)
            {
                CacheRegion cr = this.GetRegion(region);
                if (cr != null)
                {
                    lock (cr)
                    {
                        try
                        {
                            bool addresult = cr.Add(key, obj, expire);
                            if (!addresult)
                            {
                                result.Code = CacheCode.KeyHadExist;
                            }
                        }
                        catch (Exception ex)
                        {
                            _log.Fatal(ex.ToString());
                            result.Code = ex.Message;
                        }
                    }
                }
                else
                {
                    result.Code = CacheCode.RegionNotExist;
                }
            }
            else
            {
                result.Code = CacheCode.MemoryLimitReached;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Set(string region, string key, string obj, TimeSpan expire, bool addIfNotExist)
        {
            ServerResult result = new ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        cr.Set(key, obj, expire, addIfNotExist);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] SetWithLock(string region, string key, string obj, TimeSpan expire, string lockSession, bool addIfNotExist)
        {
            ServerResult result = new ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        cr.SetWithLock(key, obj, expire, lockSession, addIfNotExist);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Get(string region, string key)
        {
            GetServerResult result = new GetServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result = cr.Get(key);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithLock(string region, string key, string lockSession, int lockSeconds)
        {
            GetServerResult result = new GetServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result = cr.GetWithLock(key, lockSession, lockSeconds);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilter(string region, string filterExpression)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilter(filterExpression);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterAndLock(string region, string filterExpression, string lockSession, int lockSeconds)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterAndLock(filterExpression, lockSession, lockSeconds);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterAndTop(string region, string filterExpression, int top, string sort)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterAndTop(filterExpression, top, sort);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterAndTopAndLock(string region, string filterExpression, int top, string sort, string lockSession, int lockSeconds)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterAndTopAndLock(filterExpression, top, sort, lockSession, lockSeconds);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterUsingView(string region, string filterExpression, string view)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterUsingView(filterExpression, view);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterAndLockUsingView(string region, string filterExpression, string view, string lockSession, int lockSeconds)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterAndLockUsingView(filterExpression, view, lockSession, lockSeconds);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterAndTopUsingView(string region, string filterExpression, string view, int top, string sort)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterAndTopUsingView(filterExpression, view, top, sort);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithFilterAndTopAndLockUsingView(string region, string filterExpression, string view, int top, string sort, string lockSession, int lockSeconds)
        {
            GetWithFilterServerResult result = new GetWithFilterServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithFilterAndTopAndLockUsingView(filterExpression, view, top, sort, lockSession, lockSeconds);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithExtension(string region, string extensionName, string extensionValue)
        {
            GetWithExtensionServerResult result = new GetWithExtensionServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithExtension(extensionName, extensionValue);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] GetWithExtensionAndLock(string region, string extensionName, string extensionValue, string lockSession, int lockSeconds)
        {
            GetWithExtensionServerResult result = new GetWithExtensionServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Jsons = cr.GetWithExtensionAndLock(extensionName, extensionValue, lockSession, lockSeconds);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Count(string region)
        {
            CountServerResult result = new CountServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.Count = cr.Count();
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Count2(string region)
        {
            Count2ServerResult result = new Count2ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.AllCacheJsons = cr.Count2();
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Count2WithFilter(string region, string filterExpression)
        {
            Count2ServerResult result = new Count2ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.AllCacheJsons = cr.Count2WithFilter(filterExpression);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Count2WithExtension(string region, string extensionName, string extensionValue)
        {
            Count2ServerResult result = new Count2ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        result.AllCacheJsons = cr.Count2WithExtension(extensionName, extensionValue);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            else
            {
                result.Code = CacheCode.RegionNotExist;
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] Remove(string region, string key)
        {
            ServerResult result = new ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        cr.Remove(key);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public byte[] RemoveWithLock(string region, string key, string lockSession)
        {
            ServerResult result = new ServerResult();
            CacheRegion cr = this.GetRegion(region);
            if (cr != null)
            {
                lock (cr)
                {
                    try
                    {
                        cr.RemoveWithLock(key, lockSession);
                    }
                    catch (Exception ex)
                    {
                        _log.Fatal(ex.ToString());
                        result.Code = ex.Message;
                    }
                }
            }
            return ResultProcessor.SerializeResult(_transferCompressSize, result);
        }

        public void Dispose()
        {
            lock (_regions)
            {
                foreach (KeyValuePair<string, CacheRegion> kvp in _regions)
                {
                    kvp.Value.Dispose();
                }
            }
            if (_memwatcher != null)
            {
                _memwatcher.Abort();
            }
        }
    }
}
